记一次自动翻译导致的crash
点击上方蓝字,关于我们
大家好,我们又见面了,这次我们的Zoey同学发现了一个页面崩溃的诡异问题,经过抽丝剥茧的探索,终于找出了原因。
正文共:2720字 16图
预计阅读时间:7分钟
起因
接到一个反馈说用 lark 打开公司的一个 survey 系统页面会概率性白屏,但是又不能稳定复现。
接到一个反馈说lark打开survey的h5页面后,点击第8道的选项(该题目的选中项影响后续题目的展示),概率性白屏
排查过程
•1、pc打开没问题,手机原生浏览器打开没问题,初步判断是lark webview有额外操作•2、通过错误监控系统定位到报错•(1) HierarchyRequestError: Failed to execute 'appendChild' on 'Node': The new child element contains the parent.•(2)看起来是react源码报错,感觉操作了dom•3、让用户用后门挂上vconsole,想看看堆栈,但是没有复现,用户说“概率性”发生,时好时坏•4、我也挂上vconsole本机调试,无法复现bug但是发现加载页面后log完全不同,用户的log看起来lark进行了自动翻译⬇️
•5、用户关闭手机 App 自动翻译后果然好了,至于“概率性”发生是因为,用户开启自动翻译成中文,而页面本身就是中文,App 语言检测存在缺陷,导致有时会依然将中文翻译成中文。如果我开启自动翻译为日文,就变成了稳定复现。所以究竟为什么报错呢!
报错原因
•1、盲猜 App 的翻译是类google的(经 App 同学确认后,的确是的,此处额外感谢oncall同学的hotfix),所以我用google自动翻译试了一下,果然点了第8题的选项就crash
•2、再看看google是怎么实现的翻译,检测TEXT_NODE然后用font
包裹替换的方式⬇️
•3、回到复现路径和报错信息,点击第8道的选项 = 'removeChild' 报错,说明在点击选项后,react元素的可见发生了变化,而'removeChild' 报错说明react保存的父节点已经找不到原本的被翻译前的子节点,可能是因为原本的纯文本节点被套了font,尝试复现https://codesandbox.io/s/unruffled-cloud-8qech?file=/src/App.js:
export default function App() {
const [showText, handleShow] = useState(true);
useEffect(() => {
const nodes = document.getElementById("App").childNodes;
for (const childNode of nodes) {
// auto translating
if (childNode.nodeType === Node.TEXT_NODE) {
const fontNode = document.createElement("font");
// pretend that 'Hello CodeSandbox' has been translated
fontNode.textContent = childNode.data.toUpperCase();
childNode.parentElement.insertBefore(fontNode, childNode);
childNode.parentElement.removeChild(childNode);
}
}
}, []);
return (
<div id="App">
{showText && "Hello CodeSandbox"}
<button onClick={(e) => handleShow(false)}>Click to hide</button>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
•4、道理我懂了,开始找HTML中裸露的纯文本吧。因为每个题目区域都是conditionally render, 所以里面的每个部分都可类比上面的"HELLO CODESANDBOX", 所以筛选原则就是⬇️•nodeType = TEXT_NODE•不是父节点的唯一一个子节点
function findPureTextNodes() {
return $$('*').reduce((acc, node) => {
if (node.childNodes) {
const nodes = Array.from(node.childNodes);
return acc.concat(nodes.length > 1
? (nodes.find(child => child.nodeType === Node.TEXT_NODE) || [])
: []
)
}
return acc;
}, [])
.map(node => node.data)
.filter(text => !!text);
}
•5、🌚 这一堆0,懂了,你们这些该死的题号......
解决方案
•一开始的hotfix本想直接添加attribute,因为根据https://cloud.google.com/translate/troubleshooting, google能通过给html加标记来禁止翻译,但是lark没有实现这个功能⬇️• <span translate="no"> </span>
• <span class="notranslate"> </span>
• 捋清了crash逻辑后,给裸露纯文本包一下span就好了,debug一年,修复一分钟。。撒花结束
• 问题解决了,不过增加了多余的dom,因为题号只是想小于10的时候补0,所以我们还可以:
The End
如果你觉得这篇文章对你有帮助,有启发,我想请你帮我2个小忙:
1、点个「在看」,让更多的人也能看到这篇文章内容;
2、关注公众号「豆皮范儿」,公众号后台回复「加群」 加入我们一起学习;
关注公众号的福利持续更新,公众号后台送学习资料:
1、豆皮范儿后台回复「vis」,还可以获取更多可视化免费学习资料。
2、豆皮范儿后台回复「webgl」,还可以获取webgl免费学习资料。
3、豆皮范儿后台回复「算法」,还可以获取算法的学习资料。
4、豆皮范儿后台回复「招聘」,获取各种内推。
字节跳动数据平台前端团队,在公司内负责大数据相关产品的研发。我们在前端技术上保持着非常强的热情,除了数据产品相关的研发外,在数据可视化、海量数据处理优化、web excel、WebIDE、私有化部署、工程工具都方面都有很多的探索和积累,有兴趣可以与我们联系。
投递简历,更多精彩文章,欢迎关注 “豆皮范儿”
点个赞,证明你还爱我